package com.lin.cloud.disk.shiro.realm;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.lin.cloud.disk.common.constant.RedisConstant;
import com.lin.cloud.disk.entity.pojo.Department;
import com.lin.cloud.disk.entity.pojo.Permission;
import com.lin.cloud.disk.shiro.utils.JwtUtil;
import com.lin.cloud.disk.manage.IPermissionManageService;
import com.lin.cloud.disk.manage.IDepartmentManageService;
import com.lin.cloud.disk.shiro.pojo.JwtToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * JWT认证器
 * @author ljc
 * @date 2020/12/10 17:00
 */
@Slf4j
public class JwtRealm extends AuthorizingRealm {


    @Autowired
    private IDepartmentManageService departmentManageService;

    @Autowired
    private IPermissionManageService permissionManageService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 多重写一个support
     * 标识这个Realm是专门用来验证JwtToken
     * 不负责验证其他的token（UsernamePasswordToken）
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        //这个token就是从过滤器中传入的jwtToken
        return token instanceof JwtToken;
    }

    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("JwtRealm AuthorizationInfo");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        JwtUtil jwtUtil = new JwtUtil();
        // 1. 获取jwt
        String jwt = principals.getPrimaryPrincipal().toString();
        // 2.判断jwt是否过期
        if (jwt == null) {
            throw new NullPointerException("jwtToken 不允许为空");
        }
        if (!jwtUtil.isVerify(jwt)) {
            throw new UnknownAccountException();
        }
        // 3.解析出用户ID
        Long userId = JwtUtil.getUserId(jwt);
        String departmentKey = RedisConstant.SHIRO_ROLES + userId;
        String permissionKey = RedisConstant.SHIRO_PERMISSIONS + userId;
        // 3.1 通过redis获取
        Set<String> departments = stringRedisTemplate.opsForSet().members(departmentKey);
        Set<String> permissions = stringRedisTemplate.opsForSet().members(permissionKey);
        // 3.2 当两则都不为空时
        if (CollectionUtil.isNotEmpty(departments) && CollectionUtil.isNotEmpty(permissions)){
            // 3.3. 添加部门与权限
            info.addRoles(departments);
            info.addStringPermissions(permissions);
            log.info("从缓存中获取成功,无需重新查询");
            return info;
        }
        // 4. 通过用户Id获取部门
        List<Department> departmentList = departmentManageService.getDepartmentByUserId(userId);
        List<Long> departmentIdList = new ArrayList<>(departmentList.size());
        // 5. 解析出部门
        Set<String> departmentSet = new HashSet<>();
        departmentList.forEach(department -> {
            departmentIdList.add(department.getId());
            departmentSet.add(JSONUtil.toJsonStr(department));
        });
        // 6. 通过部门ID列表获取权限
        Set<String> permissionSet = new HashSet<>();
        List<Permission> permissionList = permissionManageService.getPermissionByDepartmentIdList(departmentIdList);
        List<String> permissionJsonList = new ArrayList<>(permissionList.size());
        permissionList.forEach(permission -> {
            permissionSet.add(JSONUtil.toJsonStr(permission));
            permissionJsonList.add(JSON.toJSONString(permission));
        });
        // 7. 添加部门与权限
        info.addRoles(departmentSet);
        info.addStringPermissions(permissionSet);
        if (departmentSet.size() != 0) {
            // 9. 存入redis
            stringRedisTemplate.opsForSet().add(departmentKey, departmentSet.toArray(new String[departmentSet.size()]));
            stringRedisTemplate.expire(departmentKey,30, TimeUnit.DAYS);
        }
        if (permissionSet.size() != 0) {
            stringRedisTemplate.opsForSet().add(permissionKey, permissionSet.toArray(new String[permissionSet.size()]));
            String cacheKey = RedisConstant.USER_PERMISSION_LIST + userId;
            stringRedisTemplate.opsForList().rightPushAll(cacheKey, CollectionUtil.toCollection(permissionJsonList));
            stringRedisTemplate.expire(permissionKey,30, TimeUnit.DAYS);
            stringRedisTemplate.expire(cacheKey,30, TimeUnit.DAYS);
        }
        log.info("departmentSet {}",departmentSet.toString());
        log.info("permissionSet {}",permissionSet.toString());
        return info;
    }

    /**
     *   认证
     *   这个token就是从过滤器中传入的jwtToken
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String jwt = (String) token.getPrincipal();
        log.info("jwt:{}",jwt);

        JwtUtil jwtUtil = new JwtUtil();

        // 1.判断jwt是否过期
        if (jwt == null) {
            throw new NullPointerException("jwtToken 不允许为空");
        }

        if (!jwtUtil.isVerify(jwt)) {
            throw new UnknownAccountException();
        }


        // 2. 获取用户ID
        Long userId = JwtUtil.getUserId(jwt);

        // 3. 判断是否在redis中存在

        String redisToken = stringRedisTemplate.opsForValue().get(RedisConstant.JWT_ACCESS_TOKEN + userId);

        // 3.1 如果redis中存在 则返回正确
        if (jwt.equals(redisToken)){
            return new SimpleAuthenticationInfo(jwt,jwt,"JwtRealm");
        }

        // 4. 从数据库匹配....
        return null;

    }


}